/*
 * SonarSource :: .NET :: Core
 * Copyright (C) 2014-2025 SonarSource Sàrl
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the Sonar Source-Available License for more details.
 *
 * You should have received a copy of the Sonar Source-Available License
 * along with this program; if not, see https://sonarsource.com/license/ssal/
 */
package org.sonarsource.dotnet.shared.plugins.protobuf;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.function.UnaryOperator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.TextRange;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.symbol.NewSymbol;
import org.sonar.api.batch.sensor.symbol.NewSymbolTable;
import org.sonarsource.dotnet.protobuf.SonarAnalyzer;
import org.sonarsource.dotnet.protobuf.SonarAnalyzer.SymbolReferenceInfo;

import static org.sonarsource.dotnet.shared.plugins.SensorContextUtils.toTextRange;

/**
  This class is responsible for reading/importing the symbol references generated by the analyzer (C# or VB.NET).
 */
public class SymbolRefsImporter extends ProtobufImporter<SonarAnalyzer.SymbolReferenceInfo> {
  private static final Logger LOG = LoggerFactory.getLogger(SymbolRefsImporter.class);
  private final SensorContext context;
  private final HashMap<InputFile, HashSet<SonarAnalyzer.SymbolReferenceInfo.SymbolReference>> fileSymbolReferences = new HashMap<>();

  public SymbolRefsImporter(SensorContext context, UnaryOperator<String> toRealPath) {
    super(SonarAnalyzer.SymbolReferenceInfo.parser(), context, SonarAnalyzer.SymbolReferenceInfo::getFilePath, toRealPath);
    this.context = context;
  }

  private static void addReferences(InputFile file, SymbolReferenceInfo.SymbolReference tokenInfo, NewSymbolTable symbolTable) {
    var declarationRange = toTextRange(file, tokenInfo.getDeclaration());
    if (declarationRange.isPresent()) {
      NewSymbol symbol = symbolTable.newSymbol(declarationRange.get());
      for (SonarAnalyzer.TextRange refTextRange : tokenInfo.getReferenceList()) {
        var validatedReference = validatedReference(file, refTextRange, declarationRange.get());
        validatedReference.ifPresent(symbol::newReference);
      }
    }
  }

  private static Optional<TextRange> validatedReference(InputFile file, SonarAnalyzer.TextRange refTextRange, TextRange declarationRange) {
    var referenceRange = toTextRange(file, refTextRange);
    if (referenceRange.isEmpty()) {
      if (LOG.isDebugEnabled()) {
        LOG.debug("The reported token was out of the range. File {}, Range {}", file.filename(), refTextRange);
      }
      return Optional.empty();
    } else if (declarationRange.overlap(referenceRange.get())) {
      if (LOG.isDebugEnabled()) {
        LOG.debug("The declaration token at {} overlaps with the referencing token {} in file {}", declarationRange, referenceRange.get(), file.filename());
      }
      return Optional.empty();
    }
    return referenceRange;
  }

  @Override
  void consumeFor(InputFile inputFile, SymbolReferenceInfo message) {
    for (SonarAnalyzer.SymbolReferenceInfo.SymbolReference tokenInfo : message.getReferenceList()) {
      fileSymbolReferences
        .computeIfAbsent(inputFile, f -> new HashSet<>())
        .add(tokenInfo);
    }
  }

  @Override
  public void save() {
    for (Map.Entry<InputFile, HashSet<SonarAnalyzer.SymbolReferenceInfo.SymbolReference>> entry : fileSymbolReferences.entrySet()) {
      NewSymbolTable symbolTable = context.newSymbolTable().onFile(entry.getKey());
      for (SonarAnalyzer.SymbolReferenceInfo.SymbolReference tokenInfo : entry.getValue()) {
        addReferences(entry.getKey(), tokenInfo, symbolTable);
      }
      symbolTable.save();
    }
  }

  @Override
  boolean isProcessed(InputFile inputFile) {
    // we aggregate all symbol reference information, no need to process only the first protobuf file
    return false;
  }
}
